#include "Objects.h"
#include "Timer.h"
#include <ctime>

// Constructors
Objects::Objects(void)
{
    name = "BlankObject";

    position.set(0,0,0);
    accel.set(0,0,0);
    velocity.set(0,0,0);

    direction.set(0,0,1);
    up.set(0,1,0);
    right.set(1,0,0);

    scale = 1;
    xRot = 0;
    yRot = 0;

    isSmoothed = false;
}
Objects::Objects(std::string filename)
{
    name = filename;

    position.set(0,0,0);
    accel.set(0,0,0);
    velocity.set(0,0,0);

    direction.set(0,0,1);
    up.set(0,1,0);
    right.set(1,0,0);

    scale = 1;
    xRot = 0;
    yRot = 0;

    isSmoothed = false;

    loadObject(filename);
    findCenter();
}
Objects::Objects(std::string filename, float x, float y, float z)
{
    name = filename;

    position.set(x,y,z);
    accel.set(0,0,0);
    velocity.set(0,0,0);

    direction.set(0,0,1);
    up.set(0,1,0);
    right.set(1,0,0);

    scale = 1;
    xRot = 0;
    yRot = 0;

    isSmoothed = false;

    loadObject(filename);
    findCenter();
}
Objects::Objects(std::string filename, Vector3f &pos)
{
    name = filename;

    position = pos;
    accel.set(0,0,0);
    velocity.set(0,0,0);

    direction.set(0,0,1);
    up.set(0,1,0);
    right.set(1,0,0);

    scale = 1;
    xRot = 0;
    yRot = 0;

    isSmoothed = false;

    loadObject(filename);
    findCenter();
}
Objects::Objects(std::string filename, Vector3f &pos, float _scale)
{
    name = filename;

    position = pos;
    accel.set(0,0,0);
    velocity.set(0,0,0);

    direction.set(0,0,1);
    up.set(0,1,0);
    right.set(1,0,0);

    scale = _scale;
    xRot = 0;
    yRot = 0;

    isSmoothed = false;

    loadObject(filename);
    findCenter();
}
Objects::~Objects()
{
    // Deconstructor
}

// Structs
Material::Material(const char* na, float _al, float _ns, float _ni, float * _di, float * _am, float *_sp, float * _tf, int ill, GLuint tex){
    name=na;
    alpha=_al;
    ns=_ns;
    ni=_ni;

    dif[0]=_di[0];
    dif[1]=_di[1];
    dif[2]=_di[2];

    amb[0]=_am[0];
    amb[1]=_am[1];
    amb[2]=_am[2];

    spec[0]=_sp[0];
    spec[1]=_sp[1];
    spec[2]=_sp[2];

    tran[0]=_tf[0];
    tran[1]=_tf[1];
    tran[2]=_tf[2];

    illum=ill;
    texture=tex;
}
textCoord::textCoord(float a, float b){
    u=a;
    v=b;
}

// Loaders
void Objects::loadObject(std::string filename){
    std::clock_t start;
    start = clock();

    float _x,_y,_z;
    char c = 'e';
    char temp[256], matLib[128];
    int whichIndex = 0, findMat = -1, curMat = -1;
    unsigned int faceCount = 0, toZ = 0;
    bool readingFace = false, hasMaterial = false;
    
    faces.push_back(FaceVal());

    std::ifstream in(filename);

    if(in.fail())
    {
        exit(1);
    }
    while(in >> c)
    {
        //in.get(c);
        //std::cout << c;

        // Reads vertices, normals, and textures
        if(c == 'v')
        {
            in.get(c);
            // Read Vertices
            if(c == ' ')
            {
                in.getline(temp,30,' ');
                _x = atof(temp);
                in.getline(temp,30,' ');
                _y = atof(temp);
                in.getline(temp,30,'\n');
                _z = atof(temp);

                vertices.push_back(Vector3f(_x,_y,_z));
                //std::cout << " " << _x << " " << _y << " " << _z << std::endl;
            }
            //Read Normals
            else if(c == 'n')
            {
                in.get(c);

                if(c == ' ')
                {
                    in.getline(temp,30,' ');
                    _x = atof(temp);
                    in.getline(temp,30,' ');
                    _y = atof(temp);
                    in.getline(temp,30,'\n');
                    _z = atof(temp);

                    normals.push_back(Vector3f(_x,_y,_z));
                    //std::cout << " " << _x << " " << _y << " " << _z << std::endl;
                }
            }
            //Read Textures
            else if(c == 't')
            {
                in.get(c);

                if(c == ' ')
                {
                    in.getline(temp,30,' ');
                    _x = atof(temp);
                    in.getline(temp,30,'\n');
                    _y = 1.0-atof(temp);

                    textureVs.push_back(textCoord(_x,_y));
                    //std::cout << " " << _x << " " << _y << std::endl;
                }
            }
        }
        /*else if(c == 's')
        {
        }
        else if(c == 'g')
        {
        }*/
        else if(c == 'm'){
            in.getline(temp,128,' ');
            if(!strcmp(temp,"tllib")){
                // Loads Material
                in.getline(temp,128);
                loadMaterial(temp);
                hasMaterial = true;
            }
        }
        else if(c == 'u' && hasMaterial)
        {
            in.getline(matLib,10,' ');
            if(!strcmp(matLib,"semtl"))
            {
                in >> matLib;
                for(findMat = 0; materials[findMat].name.compare(matLib) && findMat != materials.size()-1; findMat++){
                    // Do Nothing: This Matches material indexes based on name
                }
                curMat = findMat;
            }
            else{
                continue;
            }
        }
        // Read Faces
        else if(c == 'f' || readingFace)
        {
            if(!readingFace)
                in.get(c);
            else
                in.unget();

            if(c == ' ' || readingFace)
            {
                // Make room for next face
                readingFace = true;

                // Index of Vertex
                in.getline(temp,30,'/');
                _x = atoi(temp);

                // Texture Code
                in.getline(temp,30,'/');
                _y = atoi(temp);

                // Index of Normal
                while(in.peek() != ' ' && in.peek() != '\n'){
                    in.get(c);
                    temp[toZ] = c;
                    toZ++;
                }
                temp[toZ]='\0';
                toZ=0;
                if(in.peek() == '\n'){
                    readingFace = false;
                    faces.push_back(FaceVal());
                }
                _z = atoi(temp);

                faces[faceCount].vertexIndex[whichIndex] = _x;
                faces[faceCount].textureIndex[whichIndex] = _y;
                faces[faceCount].normalIndex[whichIndex] = _z;
                /*std::cout << _x << "/" << _y << "/" << _z << "\t";
                /*if(faces[faceCount].edges > 3)
                std::cout << "\n" << faces[faceCount].edges << "\t" << temp << "\n";*/

                if(!readingFace){
                    faces[faceCount].mat = curMat;
                    curMat = -1;
                    faceCount++;
                    whichIndex = 0;
                    //std::cout<< std::endl;
                }
                else{
                    whichIndex++;
                    faces[faceCount].edges++;
                }
                //std::cout << std::endl;
            }            
        }
        // Discards Comments
        else
        {
            in.ignore(512,'\n');
            continue;
        }
    }
    in.close();
    // Tests Build Time
    std::cout << "Obj Loader: " << (clock() - start) / (double)CLOCKS_PER_SEC << std::endl;
}
void Objects::loadMaterial(std::string filename){
    std::clock_t start;
    start = clock();

    char _na[256];
    float alp = 1, _ns, _ni;
    float _di[3], _am[3], _sp[3], _tf[3];
    int ill;
    GLuint tex;
    bool first = true;
    char temp[256];
    char imgName[30];

    std::string open = "Objects/";
    open.append(filename);
    std::ifstream mtLib(open);

    while(mtLib >> temp)
    {
        if(!strcmp(temp,"newmtl"))
        {
            //Saves current info
            if(!first){
                materials.push_back(Material(_na,alp,_ns,_ni,_di,_am,_sp,_tf,ill,tex));
            }
            else
                first = false;

            alp = _ns = _ni = 1.0f;
            _di[0] = 0;
            _di[1] = 0;
            _di[2] = 0;
            _am[0] = 0;
            _am[1] = 0;
            _am[2] = 0;
            _sp[0] = 0;
            _sp[1] = 0;
            _sp[2] = 0;
            _tf[0] = 1;
            _tf[1] = 1;
            _tf[2] = 1;

            ill = 1;
            tex = 0;

            mtLib.ignore(1);
            mtLib.getline(_na,256);
        }
        else if(!strcmp(temp,"illum")){
            // Illumination Model #
            mtLib >> ill;
        }
        else if(!strcmp(temp,"Kd")){
            //Diffuse
            mtLib >> 
                _di[0] >> 
                _di[1] >> 
                _di[2];
        }
        else if(!strcmp(temp,"map_Kd")){            
            mtLib >> imgName;
            memset(temp,NULL,strlen(temp));
            strcpy_s(temp,"Objects/");
            strcat_s(temp,imgName);

            tex = loadTexture(temp);
        }
        else if(!strcmp(temp,"Ka")){
            // Ambient Reflectivity
            mtLib >> 
                _am[0] >> 
                _am[1] >>
                _am[2];
        }
        else if(!strcmp(temp,"Tf")){
            // Transmission Filter
            // Transparency
            mtLib >> 
                _tf[0] >> 
                _tf[1] >> 
                _tf[2];
        }
        else if(!strcmp(temp,"Ni")){
            // Index of Refraction
            mtLib >> _ni;
        }
        else if(!strcmp(temp,"d")){
            // Diffuse
        }
        else if(!strcmp(temp,"Ks")){
            // Spectral Reflectivity
            mtLib >> 
                _sp[0] >> 
                _sp[1] >> 
                _sp[2];
        }
        else if(!strcmp(temp,"Ns")){
            // Specular Highlight
            mtLib >> _ns;
        }
        else
        {
            mtLib.ignore(512,'\n');
            continue;
        }
    }
    materials.push_back(Material(_na,alp,_ns,_ni,_di,_am,_sp,_tf,ill,tex));

    std::cout << "Mat Loader: " << (clock() - start) / (double)CLOCKS_PER_SEC << std::endl;
}
GLuint Objects::loadTexture(const char filename[]){
    GLuint texture;                     // This will be the texture ID
    ILuint imageID;				        // Create an image ID as a ULuint
    ILboolean success;			        // Create a flag to keep track of success/failure
    ILenum error;				        // Create a flag to keep track of the IL error state
    ilGenImages(1, &imageID); 		    // Generate the image ID 
    ilBindImage(imageID); 			    // Bind the image 
    success = ilLoadImage(filename); 	// Load the image file

    if(success){
        // Convert the image into a suitable format to work with
        // NOTE: If your image contains alpha channel you can replace IL_RGB with IL_RGBA
        success = ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE);

        // Quit out if we failed the conversion
        if (!success)
        {
            error = ilGetError();
            std::cout << "Image conversion failed - IL reports error: " << error << " - " << iluErrorString(error) << std::endl;
            return NULL;
        }

        // Generate a new texture
        glGenTextures(1, &texture);

        // Bind the texture to a name
        glBindTexture(GL_TEXTURE_2D, texture);

        
        glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
        //glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
                
        // Specify the texture specification
		glTexImage2D(GL_TEXTURE_2D, 				// Type of texture
					    0,				// Pyramid level (for mip-mapping) - 0 is the top level
					    ilGetInteger(IL_IMAGE_BPP),	// Image colour depth
					    ilGetInteger(IL_IMAGE_WIDTH),	// Image width
					    ilGetInteger(IL_IMAGE_HEIGHT),	// Image height
					    0,				// Border width in pixels (can either be 1 or 0)
					    ilGetInteger(IL_IMAGE_FORMAT),	// Image format (i.e. RGB, RGBA, BGR etc.)
					    GL_UNSIGNED_BYTE,		// Image data type
					    ilGetData());			// The actual image data itself
    }
    else // If we failed to open the image file in the first place...
    {
        error = ilGetError();
        std::cout << "Image load failed - IL reports error: " << error << " - " << iluErrorString(error) << std::endl;
        return NULL;
    }

    ilDeleteImages(1, &imageID); // Because we have already copied image data into texture data we can release memory used by image.

    std::cout << "Texture creation successful." << std::endl;

    return texture;
}
void Objects::smooth(){
    // Manual Smoothing if needed
}

// Draw
void Objects::draw()
{
    for(unsigned int x = 0; x < faces.size()-1; x++)
    {
        if(faces[x].mat != -1)
        {
            float diffuse[] = {
                materials[faces[x].mat].dif[0],
                materials[faces[x].mat].dif[1],
                materials[faces[x].mat].dif[2],
                materials[faces[x].mat].alpha};
            float ambience[] = {
                materials[faces[x].mat].amb[0],
                materials[faces[x].mat].amb[1],
                materials[faces[x].mat].amb[2],
                materials[faces[x].mat].alpha};
            float specular[] = {
                materials[faces[x].mat].spec[0],
                materials[faces[x].mat].spec[1],
                materials[faces[x].mat].spec[2],
                materials[faces[x].mat].alpha};

            glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);
            glMaterialfv(GL_FRONT,GL_AMBIENT,ambience);
            glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
            glMaterialf(GL_FRONT,GL_SHININESS,materials[faces[x].mat].ns);

            if(materials[faces[x].mat].texture == NULL)
                glDisable(GL_TEXTURE_2D);
            else
            {
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D,materials[faces[x].mat].texture);
            }
        }
        else{
            glMaterialf(GL_FRONT,GL_DIFFUSE,0.5f);
            glMaterialf(GL_FRONT,GL_AMBIENT,0.5f);
            glMaterialf(GL_FRONT,GL_SPECULAR,1);
            glMaterialf(GL_FRONT,GL_SHININESS,60);
        }

	    glBegin(GL_POLYGON);//Start drawing a Polygon
        
        for(int i = 0; i < faces[x].edges; i++)
        {
            // Normals
            if((unsigned int)faces[x].normalIndex[i]-1 > normals.size()){
                std::cerr << "ERROR: Normal Index out of range: Draw error\n"
                    << "Face " << x << "\t NormalIndex[0] = "
                    << faces[x].normalIndex[i]-1 << std::endl;
                break;
            }
            glNormal3f(
                normals[faces[x].normalIndex[i]-1].x,
                normals[faces[x].normalIndex[i]-1].y,
                normals[faces[x].normalIndex[i]-1].z);

            if(isSmoothed)
            {
                glNormal3f(
                    smoothNormals[faces[x].normalIndex[i]-1].x,
                    smoothNormals[faces[x].normalIndex[i]-1].y,
                    smoothNormals[faces[x].normalIndex[i]-1].z);
            }

            // Creates Face via Vertices
            if((unsigned int)faces[x].vertexIndex[i]-1 > vertices.size()){
                std::cerr << "ERROR: Vertex Index out of range: draw error\n"
                << "Face " << x << "\t VertexIndex[" << i << "] = "
                << faces[x].vertexIndex[i]-1 << std::endl;
                break;
            }

            //if(xRot)
            {
                CSR_vertex = new Quaternion((vertices[faces[x].vertexIndex[i]-1]-center)*scale);
                CSR_vertex->rotateAround(up, xRot);

                if(!((unsigned int)faces[x].textureIndex[i] > textureVs.size()-1
                    || faces[x].textureIndex[i] < 0) )
                {
                    glTexCoord2f(
                        textureVs[faces[x].textureIndex[i]].u,
                        textureVs[faces[x].textureIndex[i]].v);
                }
                else{
                    /*std::cerr << "ERROR: Texture Index out of range: draw error\n"
                    << "Face " << x << "\t textureIndex[" << i << "] = "
                    << faces[x].textureIndex[i] << std::endl;
                    faces[x].textureIndex[i] = 0;
                    glDisable(GL_TEXTURE_2D);*/
                    break;
                }
                glVertex3f(
                    (CSR_vertex->x) + position.x,
                    (CSR_vertex->y) + position.y,
                    (CSR_vertex->z) + position.z);

                delete CSR_vertex;
            }
        }
        glEnd(); //Stop drawing triangles
    }
    drawVertices();
}
void Objects::drawVertices()
{
    glLineWidth(30.0);
    glColor3f(0,1,0);
    for(unsigned int x = 0; x < vertices.size(); x++)
    {
        glBegin(GL_POINTS);
	    CSR_vertex = new Quaternion((vertices[x]-center)*scale);
        CSR_vertex->rotateAround(up, xRot);
                
        glVertex3f(
            (CSR_vertex->x) + position.x,
            (CSR_vertex->y) + position.y,
            (CSR_vertex->z) + position.z);

        delete CSR_vertex;
	    glEnd();
    }
}

Vector3f Objects::findCenter(){
    Vector3f bottom, top;
    top = vertices[0];
    bottom = vertices[0];

    for(unsigned int x = 1; x < vertices.size(); x++){
        if(vertices[x].y > top.y)
            top.y = vertices[x].y;
        else if(vertices[x].y < bottom.y)
            bottom.y = vertices[x].y;

        if(vertices[x].x > top.x)
            top.x = vertices[x].x;
        else if(vertices[x].x < bottom.x)
            bottom.x = vertices[x].x;

        if(vertices[x].z > top.z)
            top.z = vertices[x].z;
        else if(vertices[x].z < bottom.z)
            bottom.z = vertices[x].z;
    }
    center = ((top+bottom)*scale)/2;
    return center;
}
// Update for Collisions/Moving/Rotations
void Objects::update(){
    //Do stuff here...
}